﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace FootSteps.Domain.Loggers.Screen
{
    public class LockBitmap
    {
        Bitmap _source;
        IntPtr _iptr = IntPtr.Zero;
        BitmapData _bitmapData;
        protected DepthSetter PixelSetter;

        public byte[] Pixels { get; set; }
        public int Depth { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }

        public LockBitmap(Bitmap source)
        {
            this._source = source;
        }

        /// <summary>
        /// Lock bitmap data
        /// </summary>
        public void LockBits()
        {
            try
            {
                // Get width and height of bitmap
                Width = _source.Width;
                Height = _source.Height;

                // get total locked pixels count
                var pixelCount = Width * Height;

                // Create rectangle to lock
                var rect = new Rectangle(0, 0, Width, Height);

                // get source bitmap pixel format size
                Depth = Image.GetPixelFormatSize(_source.PixelFormat);

                // Check if bpp (Bits Per Pixel) is 8, 24, or 32
                if (Depth != 8 && Depth != 24 && Depth != 32)
                {
                    throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
                }

                // Lock bitmap and return bitmap data
                _bitmapData = _source.LockBits(rect, ImageLockMode.ReadWrite,
                    _source.PixelFormat);

                // create byte array to copy pixel values
                var step = Depth / 8;
                Pixels = new byte[pixelCount * step];
                _iptr = _bitmapData.Scan0;

                // Copy data from pointer to array
                Marshal.Copy(_iptr, Pixels, 0, Pixels.Length);

                switch (Depth)
                {
                    case 8: PixelSetter = new DepthSetter8(this); break;
                    case 24: PixelSetter = new DepthSetter24(this); break;
                    case 32: PixelSetter = new DepthSetter32(this); break;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// Unlock bitmap data
        /// </summary>
        public void UnlockBits()
        {
            try
            {
                // Copy data from byte array to pointer
                Marshal.Copy(Pixels, 0, _iptr, Pixels.Length);

                // Unlock bitmap data
                _source.UnlockBits(_bitmapData);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        protected abstract class DepthSetter
        {
            protected LockBitmap Owner;
            protected int Width;
            protected int Length;
            protected byte[] Pixels;

            protected DepthSetter(LockBitmap owner)
            {
                Owner = owner;
                Width = owner.Width;
                Length = Owner.Pixels.Length;
                Pixels = owner.Pixels;
            }

            public abstract Color GetPixel(int x, int y);
            public abstract void SetPixel(int x, int y, Color color);
        }

        /// <summary>
        ///  For 8 bpp get color value (Red, Green and Blue values are the same)
        /// </summary>
        private class DepthSetter8 : DepthSetter
        {
            public DepthSetter8(LockBitmap owner) : base(owner)
            {
            }
            
            const int CCount = 1; //Owner.Depth / 8;
            public override Color GetPixel(int x, int y)
            {                
                // Get start index of the specified pixel
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                var c = Pixels[i];
                return Color.FromArgb(c, c, c);
            }

            public override void SetPixel(int x, int y, Color color)
            {
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                Pixels[i] = color.B;
            }
        }

        /// <summary>
        /// For 24 bpp get Red, Green and Blue
        /// </summary>
        private class DepthSetter24 : DepthSetter
        {
            public DepthSetter24(LockBitmap owner)
                : base(owner)
            {
            }

            const int CCount = 3; //Owner.Depth / 8;
            public override Color GetPixel(int x, int y)
            {   
                // Get start index of the specified pixel
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                var b = Pixels[i];
                var g = Pixels[i + 1];
                var r = Pixels[i + 2];
                
                return Color.FromArgb(r, g, b);
            }

            public override void SetPixel(int x, int y, Color color)
            {
                
                // Get start index of the specified pixel
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                Pixels[i] = color.B;
                Pixels[i + 1] = color.G;
                Pixels[i + 2] = color.R;
            }
        }

        /// <summary>
        /// For 32 bpp get Red, Green, Blue and Alpha
        /// </summary>
        private class DepthSetter32 : DepthSetter
        {
            public DepthSetter32(LockBitmap owner)
                : base(owner)
            {
            }

            const int CCount = 4; //Owner.Depth / 8;
            public override Color GetPixel(int x, int y)
            {
                // Get start index of the specified pixel
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                var b = Pixels[i];
                var g = Pixels[i + 1];
                var r = Pixels[i + 2];
                var a = Pixels[i + 3]; // a
                return Color.FromArgb(a, r, g, b);
            }

            public override void SetPixel(int x, int y, Color color)
            {

                // Get start index of the specified pixel
                var i = ((y * Width) + x) * CCount;

                if (i > Length - CCount)
                    throw new IndexOutOfRangeException();

                Pixels[i] = color.B;
                Pixels[i + 1] = color.G;
                Pixels[i + 2] = color.R;
                Pixels[i + 3] = color.A;
            }
        }

        /// <summary>
        /// Get the color of the specified pixel
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public Color GetPixel(int x, int y)
        {
            return PixelSetter.GetPixel(x, y);
        }

        /// <summary>
        /// Set the color of the specified pixel
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="color"></param>
        public void SetPixel(int x, int y, Color color)
        {
            PixelSetter.SetPixel(x,y,color);
        }

        ///// <summary>
        ///// Get the color of the specified pixel
        ///// </summary>
        ///// <param name="x"></param>
        ///// <param name="y"></param>
        ///// <returns></returns>
        //public Color GetPixel(int x, int y)
        //{
        //    var clr = Color.Empty;

        //    // Get color components count
        //    var cCount = Depth / 8;

        //    // Get start index of the specified pixel
        //    var i = ((y * Width) + x) * cCount;

        //    if (i > Pixels.Length - cCount)
        //        throw new IndexOutOfRangeException();

        //    if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
        //    {
        //        var b = Pixels[i];
        //        var g = Pixels[i + 1];
        //        var r = Pixels[i + 2];
        //        var a = Pixels[i + 3]; // a
        //        clr = Color.FromArgb(a, r, g, b);
        //    }
        //    if (Depth == 24) // For 24 bpp get Red, Green and Blue
        //    {
        //        var b = Pixels[i];
        //        var g = Pixels[i + 1];
        //        var r = Pixels[i + 2];
        //        clr = Color.FromArgb(r, g, b);
        //    }
        //    if (Depth == 8)
        //        // For 8 bpp get color value (Red, Green and Blue values are the same)
        //    {
        //        var c = Pixels[i];
        //        clr = Color.FromArgb(c, c, c);
        //    }
        //    return clr;
        //}

        ///// <summary>
        ///// Set the color of the specified pixel
        ///// </summary>
        ///// <param name="x"></param>
        ///// <param name="y"></param>
        ///// <param name="color"></param>
        //public void SetPixel(int x, int y, Color color)
        //{
        //    // Get color components count
        //    var cCount = Depth / 8;

        //    // Get start index of the specified pixel
        //    var i = ((y * Width) + x) * cCount;

        //    if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
        //    {
        //        Pixels[i] = color.B;
        //        Pixels[i + 1] = color.G;
        //        Pixels[i + 2] = color.R;
        //        Pixels[i + 3] = color.A;
        //    }
        //    if (Depth == 24) // For 24 bpp set Red, Green and Blue
        //    {
        //        Pixels[i] = color.B;
        //        Pixels[i + 1] = color.G;
        //        Pixels[i + 2] = color.R;
        //    }
        //    if (Depth == 8)
        //        // For 8 bpp set color value (Red, Green and Blue values are the same)
        //    {
        //        Pixels[i] = color.B;
        //    }
        //}
    }
}